package com.hmy.security.security.config;

import com.hmy.security.security.annotation.OpenAccess;
import com.hmy.security.security.filter.TokenFilter;
import com.hmy.security.security.service.ForbidAccessDeniedHandler;
import com.hmy.security.security.service.UnauthorizedEntryPoint;
import com.hmy.security.security.service.UserDetailServiceImpl;
import com.hmy.security.util.CacheUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailServiceImpl userDetailService;

    private final UnauthorizedEntryPoint unauthorizedEntryPoint;
    private final ForbidAccessDeniedHandler forbidAccessDeniedHandler;
    private final ApplicationContext applicationContext;
    private final CorsFilter corsFilter;
    private final TokenFilter tokenFilter;

    public WebSecurityConfig(UnauthorizedEntryPoint unauthorizedEntryPoint, ForbidAccessDeniedHandler forbidAccessDeniedHandler,
                             ApplicationContext applicationContext, CorsFilter corsFilter, CacheUtil cacheUtil) {
        this.unauthorizedEntryPoint = unauthorizedEntryPoint;
        this.forbidAccessDeniedHandler = forbidAccessDeniedHandler;
        this.applicationContext = applicationContext;
        this.corsFilter = corsFilter;
        this.tokenFilter = new TokenFilter(cacheUtil);
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        // 密码加密方式
        return new BCryptPasswordEncoder();
    }

    /**
     * 去除 ROLE_ 前缀
     * e.g. @PreAuthorize("hasRole('xxx')")在进行权限判断是会在xxx前添加前缀"ROLE_" 再利用contains判断
     * 参考： {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyAuthorityName}
     */
    @Bean
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults("");
    }

    /**
     * 角色继承
     *
     * <p> 开启后管理员的权限列表中会包含["管理员", "部长", "组长", "螺丝钉"] </p>
     * <p> 不开启则只会包含管理员 </p>
     */
    @Bean
    RoleHierarchy roleHierarchy(){
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("管理员 > 部长 > 组长 > 螺丝钉");
        return roleHierarchy;
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService);

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 获取所有标注了OpenAccess的接口
        // 获取所有的RequestMapping RequestMappingHandlerMapping介绍： https://blog.51cto.com/u_3631118/3124560
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        Set<String> openAccessUrls = new HashSet<>();
        handlerMethodMap.forEach((k,v) -> {
            OpenAccess openAccess = v.getMethodAnnotation(OpenAccess.class);
            if (ObjectUtils.isNotEmpty(openAccess)) {
                openAccessUrls.addAll(k.getPatternsCondition().getPatterns());
            }
        });

        http
                // 关闭CSRF
                .csrf().disable()
                .addFilterBefore(this.corsFilter, UsernamePasswordAuthenticationFilter.class)
                // 前后端分离 每次都要通过此过滤器实现鉴权
                .addFilterBefore(this.tokenFilter, UsernamePasswordAuthenticationFilter.class)

                // 授权异常 放在后面的先被执行
                .exceptionHandling()
                // 不使用security官方提供的访问受限处理器 使用自定义的forbidAccessDeniedHandler 保证接口返回值统一
//                .authenticationEntryPoint(new Http403ForbiddenEntryPoint())
                .authenticationEntryPoint(this.unauthorizedEntryPoint)
                .accessDeniedHandler(this.forbidAccessDeniedHandler)

                // 阻止iframe
                .and()
                .headers()
                .frameOptions()
                .disable()

                // 不创建会话
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()
                .authorizeRequests()

                // 排除静态资源
                .antMatchers(
                        HttpMethod.GET,
            "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/websocket/**"
                ).permitAll()
                // 文件
                .antMatchers("/avatar/**").permitAll()
                .antMatchers("/file/**").permitAll()
                // 阿里巴巴 druid
                .antMatchers("/druid/**").permitAll()
                .antMatchers("/api/canvas/**").permitAll()
                // 放行OPTIONS请求
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

                // 公开的url放行
                .antMatchers(openAccessUrls.toArray(new String[0])).permitAll()
                // 所有请求都需要认证
                .anyRequest().authenticated();

    }



}
